package com.atguigu.tingshu.search.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.extra.pinyin.PinyinUtil;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.*;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.CompletionSuggestOption;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.HitsMetadata;
import co.elastic.clients.elasticsearch.core.search.Suggestion;
import co.elastic.clients.json.JsonData;
import com.alibaba.nacos.common.utils.CollectionUtils;
import com.alibaba.nacos.common.utils.StringUtils;
import com.atguigu.tingshu.album.AlbumFeignClient;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.model.album.*;
import com.atguigu.tingshu.model.search.AlbumInfoIndex;
import com.atguigu.tingshu.model.search.AttributeValueIndex;
import com.atguigu.tingshu.model.search.SuggestIndex;
import com.atguigu.tingshu.query.search.AlbumIndexQuery;
import com.atguigu.tingshu.repository.AlbumInfoRepository;
import com.atguigu.tingshu.repository.SuggestInfoRepository;
import com.atguigu.tingshu.search.service.SearchService;
import com.atguigu.tingshu.user.client.UserFeignClient;
import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;
import com.atguigu.tingshu.vo.search.AlbumSearchResponseVo;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.suggest.Completion;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;


@Slf4j
@Service
@SuppressWarnings({"all"})
public class SearchServiceImpl implements SearchService {

    @Autowired
    private AlbumInfoRepository albumInfoRepository;
    @Autowired
    private SuggestInfoRepository suggestInfoRepository;
    @Autowired
    private UserFeignClient userFeignClient;
    @Autowired
    private AlbumFeignClient albumFeignClient;
    @Autowired
    private ThreadPoolExecutor executor;
    @Autowired
    private ElasticsearchClient elasticsearchClient;

    private static final String INDEX_NAME = "albuminfo";
    private static final String SUGGEST_NAME = "suggestinfo";

    @Override
    public void upperAlbum(Long albumId) {
        AlbumInfoIndex albumInfoIndex = new AlbumInfoIndex();

        CompletableFuture<AlbumInfo> albumInfoCompletableFuture = CompletableFuture.supplyAsync(() -> {
            AlbumInfo albumInfo = albumFeignClient.getAlbumInfo(albumId).getData();
            Assert.notNull(albumInfo, "查询专辑信息异常,albumId:{}", albumId);
            BeanUtils.copyProperties(albumInfo, albumInfoIndex);
            albumInfoIndex.setCreateTime(new Date());

            List<AlbumAttributeValue> albumAttrValueVoList = albumInfo.getAlbumAttributeValueVoList();
            List<AttributeValueIndex> attrIndexList = albumAttrValueVoList.stream()
                    .map(albumAttrValue -> BeanUtil.copyProperties(albumAttrValue, AttributeValueIndex.class))
                    .collect(Collectors.toList());
            albumInfoIndex.setAttributeValueIndexList(attrIndexList);

            return albumInfo;
        }, executor);

        CompletableFuture<Void> categoryInfoCompletableFuture = albumInfoCompletableFuture.thenAcceptAsync(albumInfo -> {
            BaseCategoryView categoryView = albumFeignClient.getCategoryView(albumInfo.getCategory3Id()).getData();
            Assert.notNull(albumInfo, "查询分类信息异常,category3Id:{}", albumInfo.getCategory3Id());
            albumInfoIndex.setCategory1Id(categoryView.getCategory1Id());
            albumInfoIndex.setCategory2Id(categoryView.getCategory2Id());
        }, executor);

//        Result<AlbumStatVo> albumStatVo = albumFeignClient.getAlbumStatVo(albumId);

        CompletableFuture<Void> userInfoCompletableFuture = albumInfoCompletableFuture.thenAcceptAsync(albumInfo -> {
            UserInfoVo userInfoVo = userFeignClient.getUserInfoVo(albumInfo.getUserId()).getData();
            Assert.notNull(albumInfo, "查询用户信息异常,userId:{}", albumInfo.getUserId());
            albumInfoIndex.setAnnouncerName(userInfoVo.getNickname());
        }, executor);

        CompletableFuture<Void> albumStatCompletableFuture = CompletableFuture.runAsync(() -> {
            int num1 = RandomUtil.randomInt(1000, 2000);
            int num2 = RandomUtil.randomInt(500, 1000);
            int num3 = RandomUtil.randomInt(200, 400);
            int num4 = RandomUtil.randomInt(100, 200);
            albumInfoIndex.setPlayStatNum(num1);
            albumInfoIndex.setSubscribeStatNum(num2);
            albumInfoIndex.setBuyStatNum(num3);
            albumInfoIndex.setCommentStatNum(num4);


            BigDecimal bigDecimal1 = new BigDecimal(num4).multiply(new BigDecimal("0.4"));
            BigDecimal bigDecimal2 = new BigDecimal(num3).multiply(new BigDecimal("0.3"));
            BigDecimal bigDecimal3 = new BigDecimal(num2).multiply(new BigDecimal("0.2"));
            BigDecimal bigDecimal4 = new BigDecimal(num1).multiply(new BigDecimal("0.1"));
            BigDecimal hotScore = bigDecimal1.add(bigDecimal2).add(bigDecimal3).add(bigDecimal4);
            albumInfoIndex.setHotScore(hotScore.doubleValue());

        }, executor);

        CompletableFuture.allOf(albumInfoCompletableFuture, userInfoCompletableFuture, categoryInfoCompletableFuture, albumStatCompletableFuture).join();

        albumInfoRepository.save(albumInfoIndex);

        saveSuggestIndex(albumInfoIndex);
    }

    public void saveSuggestIndex(AlbumInfoIndex albumInfoIndex) {

        SuggestIndex suggestIndex = new SuggestIndex();
        suggestIndex.setId(albumInfoIndex.getId().toString());
        String albumTitle = albumInfoIndex.getAlbumTitle();
        suggestIndex.setTitle(albumTitle);
        suggestIndex.setKeyword(new Completion(new String[]{albumTitle}));
        suggestIndex.setKeywordPinyin(new Completion(new String[]{PinyinUtil.getPinyin(albumTitle, "")}));
        suggestIndex.setKeywordSequence(new Completion(new String[]{PinyinUtil.getFirstLetter(albumTitle, "")}));
        suggestInfoRepository.save(suggestIndex);

    }

    @Override
    public void lowerAlbum(Long albumId) {
        albumInfoRepository.deleteById(albumId);
    }

    @Override
    @SneakyThrows
    public AlbumSearchResponseVo searchAlbumInfo(AlbumIndexQuery query) {
        SearchRequest searchRequest = buildDsl(query);
        SearchResponse<AlbumInfoIndex> searchResponse = elasticsearchClient.search(searchRequest, AlbumInfoIndex.class);
        AlbumSearchResponseVo albumSearchResponseVo = parseResult(searchResponse,query);
        return albumSearchResponseVo;
    }

    @Override
    @SneakyThrows
    public List<String> completeSuggest(String keyword) {

        SearchResponse<SuggestIndex> searchResponse = elasticsearchClient.search(s -> s.index(SUGGEST_NAME)
                        .suggest(su -> su.suggesters("myKeyword", sug -> sug.prefix(keyword).completion(
                                        c -> c.field("keyword").fuzzy(f -> f.fuzziness("auto")).skipDuplicates(true)))
                                .suggesters("myKeywordPinyin", sug -> sug.prefix(keyword).completion(
                                        c -> c.field("keywordPinyin").fuzzy(f -> f.fuzziness("auto")).skipDuplicates(true)))
                                .suggesters("myKeywordSequence", sug -> sug.prefix(keyword).completion(
                                        c -> c.field("keywordSequence").fuzzy(f -> f.fuzziness("auto")).skipDuplicates(true))))
                , SuggestIndex.class);


        LinkedHashSet<String> resultSet = new LinkedHashSet<>();
        Map<String, List<Suggestion<SuggestIndex>>> suggest = searchResponse.suggest();
        parseSuggestResult("myKeyword",suggest,resultSet);
        parseSuggestResult("myKeywordPinyin",suggest,resultSet);
        parseSuggestResult("myKeywordSequence",suggest,resultSet);

        if (resultSet.size() >= 10) {
            return new ArrayList<>(resultSet).subList(0, 10);
        }

        if (resultSet.size() < 10) {
            SearchResponse<AlbumInfoIndex> response = elasticsearchClient.search(s -> s.index(INDEX_NAME).query(
                    q -> q.match(m -> m.field("albumTitle").query(keyword))), AlbumInfoIndex.class);
            List<Hit<AlbumInfoIndex>> hits = response.hits().hits();
            if (CollectionUtil.isNotEmpty(hits)) {
                for (Hit<AlbumInfoIndex> hit : hits) {
                    String albumTitle = hit.source().getAlbumTitle();
                    resultSet.add(albumTitle);
                    if (resultSet.size() >= 10) {
                        break;
                    }
                }
            }

        }

        return new ArrayList<>(resultSet);
    }

    @Autowired
    private RedisTemplate redisTemplate;

    @SneakyThrows
    @Override
    public List<AlbumInfoIndex> findRankingList(String category1Id, String dimension) {

        List<AlbumInfoIndex> indexList = null;

        String key = RedisConstant.RANKING_KEY_PREFIX + category1Id;
        Boolean flag = redisTemplate.opsForHash().hasKey(key, dimension);
        if (flag) {
            indexList = (List<AlbumInfoIndex>) redisTemplate.opsForHash().get(key, dimension);
        }

        return indexList;
    }

    @SneakyThrows
    @Override
    public void updateLatelyAlbumRanking() {
        List<AlbumInfoIndex> indexList = new ArrayList<>();

        List<BaseCategory1> category1List = albumFeignClient.findAllCategory1().getData();
        Assert.notNull(category1List,"获取一级分类列表失败");

        String[] dimensionList = new String[]{"hotScore", "playStatNum", "subscribeStatNum", "buyStatNum", "commentStatNum"};

        for (BaseCategory1 category1 : category1List) {

            Long category1Id = category1.getId();
            for (String dimension : dimensionList) {

                SearchResponse<AlbumInfoIndex> searchResponse = elasticsearchClient.search(s ->
                        s.index(INDEX_NAME)
                                .query(q -> q.term(t -> t.field("category1Id").value(category1Id)))
                                .sort(sr -> sr.field(f -> f.field(dimension).order(SortOrder.Desc)))
                                .size(15), AlbumInfoIndex.class);

                List<Hit<AlbumInfoIndex>> hits = searchResponse.hits().hits();
                if (CollectionUtil.isNotEmpty(hits)) {

                    indexList = hits.stream().map(hit -> hit.source()).collect(Collectors.toList());
                    String key = RedisConstant.RANKING_KEY_PREFIX + category1Id;
                    redisTemplate.opsForHash().put(key, dimension, indexList);

                }

            }
        }

    }


    @Override
    @SneakyThrows
    public List<Map<String, Object>> channel(Long category1Id) {


        List<BaseCategory3> baseCategory3List = albumFeignClient.findTopBaseCategory3(category1Id).getData();
        Assert.notNull(baseCategory3List, "查询指定一级分类下最热门的三级分类列表异常,category1Id:{}", category1Id);

        Map<Long, BaseCategory3> category3Map = baseCategory3List.stream().collect(Collectors.toMap(BaseCategory3::getId, baseCategory3 -> baseCategory3));
        List<FieldValue> category3Ids = baseCategory3List.stream().map(baseCategory3 -> FieldValue.of(baseCategory3.getId())).collect(Collectors.toList());

        SearchResponse<AlbumInfoIndex> searchResponse = elasticsearchClient.search(s -> s
                        .index(INDEX_NAME)
                        .query(q -> q.terms(t -> t.field("category3Id").terms(te -> te.value(category3Ids))))
                        .size(0)
                        .aggregations("category3Aggs", a -> a
                                .terms(te -> te.field("category3Id"))
                                .aggregations("top6AlbumAggs", ag -> ag.topHits(top -> top
                                        .size(6)
                                        .sort(so -> so.field(fi -> fi.field("hotScore").order(SortOrder.Desc))))))
                , AlbumInfoIndex.class);

        List<Map<String, Object>> albumOfCategory3List = new ArrayList<>();
        LongTermsAggregate category3Aggs = searchResponse.aggregations().get("category3Aggs").lterms();
        List<LongTermsBucket> bucketList = category3Aggs.buckets().array();
        if (CollectionUtil.isNotEmpty(bucketList)) {

            for (LongTermsBucket longTermsBucket : bucketList) {
                HashMap<String, Object> albumOfCategory3 = new HashMap<>();
                TopHitsAggregate top6AlbumAggs = longTermsBucket.aggregations().get("top6AlbumAggs").topHits();
                List<Hit<JsonData>> hits = top6AlbumAggs.hits().hits();
                List<AlbumInfoIndex> indexList = hits.stream().map(hit -> hit.source().to(AlbumInfoIndex.class)).collect(Collectors.toList());
                long category3Id = longTermsBucket.key();
                albumOfCategory3.put("list", indexList);
                albumOfCategory3.put("baseCategory3", category3Map.get(category3Id));
                albumOfCategory3List.add(albumOfCategory3);
            }
        }

        return albumOfCategory3List;
    }

    public void parseSuggestResult(String suggestName, Map<String, List<Suggestion<SuggestIndex>>> suggest, LinkedHashSet<String> resultSet) {

        List<Suggestion<SuggestIndex>> suggestionList = suggest.get(suggestName);

        for (Suggestion<SuggestIndex> suggestion : suggestionList) {

            List<CompletionSuggestOption<SuggestIndex>> options = suggestion.completion().options();
            if (CollectionUtil.isNotEmpty(options)) {

                for (CompletionSuggestOption<SuggestIndex> option : options) {

                    String title = option.source().getTitle();
                    resultSet.add(title);
                }
            }
        }
    }

    public AlbumSearchResponseVo parseResult(SearchResponse<AlbumInfoIndex> searchResponse, AlbumIndexQuery query) {
        AlbumSearchResponseVo albumSearchResponseVo = new AlbumSearchResponseVo();
        Integer pageNo = query.getPageNo();
        Integer pageSize = query.getPageSize();

        HitsMetadata<AlbumInfoIndex> indexHitsMetadata = searchResponse.hits();
        List<Hit<AlbumInfoIndex>> hits = indexHitsMetadata.hits();
        if (CollectionUtil.isNotEmpty(hits)) {

            List<AlbumInfoIndexVo> indexVoList = hits.stream().map(albumInfoIndexHit -> {
                AlbumInfoIndex source = albumInfoIndexHit.source();
                AlbumInfoIndexVo albumInfoIndexVo = BeanUtil.copyProperties(source, AlbumInfoIndexVo.class);

                // AlbumInfoIndex中的albumTitle没有高亮,需从highlight中取
                Map<String, List<String>> highlight = albumInfoIndexHit.highlight();
                if (CollectionUtil.isNotEmpty(highlight)) {
                    List<String> titleList = highlight.get("albumTitle");
                    if (CollectionUtil.isNotEmpty(titleList)) {
                        albumInfoIndexVo.setAlbumTitle(titleList.get(0));
                    }
                }
                return albumInfoIndexVo;
            }).collect(Collectors.toList());



            albumSearchResponseVo.setList(indexVoList);
        }


            Long total = indexHitsMetadata.total().value();
            Long totalPages = total % pageSize == 0 ? total / pageSize : total / pageSize + 1;
            albumSearchResponseVo.setTotal(total);
            albumSearchResponseVo.setPageNo(pageNo);
            albumSearchResponseVo.setPageSize(pageSize);
            albumSearchResponseVo.setTotalPages(totalPages);


        return albumSearchResponseVo;
    }

    public SearchRequest buildDsl(AlbumIndexQuery query) {
        SearchRequest.Builder builder = new SearchRequest.Builder();
        builder.index(INDEX_NAME);

        //构建外层多条件对象
        BoolQuery.Builder boolBuilder = new BoolQuery.Builder();  //创建内部类对象

        //获取关键字
        String keyword = query.getKeyword();
        //判断
        if (StringUtils.isNotEmpty(keyword)) {

            //构建内层多条件对象
            BoolQuery.Builder subBoolBuilder = new BoolQuery.Builder();
            //添加匹配标题
            subBoolBuilder.should(s -> s.match(m -> m.query(keyword).field("albumTitle")));

            //添加匹配简介
            subBoolBuilder.should(s -> s.match(m -> m.query(keyword).field("albumIntro")));
            //添加匹配主播
            subBoolBuilder.should(s -> s.term(t -> t.field("announcerName").value(keyword)));

            boolBuilder.must(subBoolBuilder.build()._toQuery());
        }


        //添加一二三级分类条件
        if (query.getCategory1Id() != null) {
            boolBuilder.filter(f -> f.term(t -> t.field("category1Id").value(query.getCategory1Id())));

        }
        if (query.getCategory2Id() != null) {
            boolBuilder.filter(f -> f.term(t -> t.field("category2Id").value(query.getCategory2Id())));

        }
        if (query.getCategory3Id() != null) {
            boolBuilder.filter(f -> f.term(t -> t.field("category3Id").value(query.getCategory3Id())));

        }

        //获取专辑属性条件
        List<String> attributeList = query.getAttributeList();
        //判断
        if (CollectionUtils.isNotEmpty(attributeList)) {

            for (String attribute : attributeList) {
                //attribute=属性id:属性值id
                String[] attributeArr = attribute.split(":");

                //添加专辑属性条件  str=属性id:属性值id
                boolBuilder.filter(f -> f.nested(n ->
                        n.path("attributeValueIndexList")
                                .query(q -> q.bool(
                                        b -> b.filter(f1 -> f1.term(t -> t.field("attributeValueIndexList.attributeId").value(attributeArr[0])))
                                                .filter(f2 -> f2.term(t -> t.field("attributeValueIndexList.valueId").value(attributeArr[1])))))));

            }


        }


        //设置查询条件
        builder.query(boolBuilder.build()._toQuery());

        //设置分页条件

        //获取当前页
        Integer pageNo = query.getPageNo();
        //获取每页条数
        Integer pageSize = query.getPageSize();
        //计算开始的索引
        int startIndex = (pageNo - 1) * pageSize;
        builder.from(startIndex);
        builder.size(pageSize);

        //设置条件条件--前提必须在关键字查询的前提下
        if (StringUtils.isNotEmpty(keyword)) {

            builder.highlight(hi -> hi.fields("albumTitle", fi -> fi.postTags("</font>").preTags("<font color='red'>")));

        }


        //获取排序条件
        //排序（综合排序[1:desc] 播放量[2:desc] 发布时间[3:desc]；asc:升序 desc:降序）
        //1：综合排序hotscore 2：播放量 3：最近更新
        String order = query.getOrder();
        //判断
        if(StringUtils.isNotEmpty(order)){

            //截取排序条件
            String[] split = order.split(":");
            //定义字段
            String fielder="";
            //转换字段
            switch (split[0]){
                case "1":
                    fielder="hotScore";
                    break;
                case "2":
                    fielder="playStatNum";
                    break;
                case "3":
                    fielder="createTime";
                    break;

            }


            String finalFielder = fielder;
            builder.sort(s->s.field(fi->fi.field(finalFielder).order("desc".equals(split[1])? SortOrder.Desc:SortOrder.Asc)));


        }

        //排除字段
        builder.source(so->so.filter(fi->fi.excludes("category1Id", "category2Id", "category3Id","attributeValueIndexList","hotScore")));



        SearchRequest searchRequest = builder.build();
        return searchRequest;
    }
}
